﻿using System;
using System.Reflection;
using System.Data;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Arquitetura.Architectural.DataLayer
{
    public abstract class Repository<T> : Repository, IRepository<T> where T : ObjectContext
    {
        #region Proprieadade(s)
        protected new T Context
        {
            get
            {
                return (T)base.Context;
            }

            set
            {
                base.Context = value;
            }
        }
        #endregion Proprieadade(s)

        #region Construtor(es)
        public Repository()
            : base() { }

        public Repository(MergeOption defaultMergeOption)
            : base(defaultMergeOption) { }

        public Repository(T objectContext)
            : base(objectContext) { }

        public Repository(T objectContext, MergeOption defaultMergeOption)
            : base(objectContext, defaultMergeOption) { }
        #endregion Construtor(es)
    }

    public abstract class Repository : IRepository
    {
        #region Propriedade(s)
        protected abstract string EntitySetName
        { get; }

        protected string QualifiedEntitySetName
        {
            get
            {
                return string.Concat(Context.DefaultContainerName, ".", EntitySetName);
            }
        }

        protected ObjectContext Context
        { get; set; }

        protected MergeOption DefaultMergeOption
        { get; set; }
        #endregion Propriedade(s)

        #region Construtor(es)
        public Repository()
        {
            DefaultMergeOption = MergeOption.NoTracking;
        }

        public Repository(MergeOption defaultMergeOption)
        {
            DefaultMergeOption = defaultMergeOption;
        }

        public Repository(ObjectContext objectContext)
            : this()
        {
            Context = objectContext;
        }

        public Repository(ObjectContext objectContext, MergeOption defaultMergeOption)
            : this(defaultMergeOption)
        {
            Context = objectContext;
        }
        #endregion Construtor(es)

        #region Método(s)
        protected ObjectQuery<T> ObjectQuerySelectFactory<T>() where T : EntityObject
        {
            string query = SelectQueryFactory<T>();
            ObjectQuery<T> objectQuery = new ObjectQuery<T>(query, Context);
            objectQuery.MergeOption = DefaultMergeOption;
            return objectQuery;
        }

        protected EntityKey CreateEntityKey(EntityObject entity)
        {
            return Context.CreateEntityKey(QualifiedEntitySetName, entity);
        }

        protected void InsertEntity(EntityObject entity)
        {
            Context.AddObject(QualifiedEntitySetName, entity);
            Context.SaveChanges(true);
            Context.Detach(entity);
        }

        protected void UpdateEntity(EntityObject entity)
        {
            AttachEntity(entity);
            SetModified(entity);
            Context.SaveChanges(true);
            Context.Detach(entity);
        }

        protected void DeleteEntity(EntityObject entity)
        {
            AttachEntity(entity);
            Context.DeleteObject(entity);
            Context.SaveChanges(true);
        }

        private void AttachEntity(EntityObject entity)
        {
            if (entity.EntityKey == null)
            {
                entity.EntityKey = Context.CreateEntityKey(QualifiedEntitySetName, entity);
            }
            Context.Attach(entity);
        }

        private string SelectQueryFactory<T>() where T : EntityObject
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("SELECT VALUE TREAT (obj as ");
            sb.Append(typeof(T).FullName);
            sb.Append(") FROM ");
            sb.Append(QualifiedEntitySetName);
            sb.Append(" as obj WHERE obj IS OF(ONLY ");
            sb.Append(typeof(T).FullName);
            sb.Append(")");
            return sb.ToString();
        }

        /// <summary>
        /// Marca todas as propriedades como modificadas
        /// </summary>
        /// <typeparam name="T">Tipo da Entidade</typeparam>
        /// <param name="entity">Entidade</param>
        /// <param name="context">Context de Controle de Estado de Persistencia de Entidades</param>
        private void SetModified(EntityObject entity)
        {
            var stateEntry = Context.ObjectStateManager.GetObjectStateEntry(entity.EntityKey);
            var propertyNameList = stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select(pn => pn.FieldType.Name);
            foreach (var propName in propertyNameList)
            {
                stateEntry.SetModifiedProperty(propName);
            }
        }
        #endregion Método(s)
    }
}
